﻿/*
 * Author: Brian Brewder
 * Website: http://redwerb.com
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace Redwerb.Visualizers.Controls
{
    [DefaultEvent("ValueChanged")]
    public partial class VSlider : UserControl
    {

        #region Initialization and Destruction

        public VSlider()
        {
            InitializeComponent();
        }

        #endregion

        #region Fields and Properties

        private bool mSliding = false;

        private float mValue;
        [Category("Behavior")]
        [Description("A value between 0 and 1.")]
        public float Value
        {
            get { return mValue; }
            set
            {
                value = Math.Min(1, value);
                value = Math.Max(0, value);
                if (mValue == value) return;
                mValue = value;
                UpdateKnobPosition();
                OnValueChanged(EventArgs.Empty);
            }
        }

        private List<float> mStopValues = new List<float>();
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public float[] StopValues
        {
            get { return mStopValues.ToArray(); }
        }

        private int mSnapSize = 3;
        [Category("Behavior")]
        [Description("The number of pixels that determine the bounds for the stop points.")]
        [DefaultValue(3)]
        public int SnapSize
        {
            get { return mSnapSize; }
            set { mSnapSize = value; }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        private int MinTop
        {
            get { return pnlGroove.Top - (picKnob.Height / 2); }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        private int MaxTop
        {
            get { return pnlGroove.Bottom - (picKnob.Height / 2); }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        private int Distance
        {
            get { return pnlGroove.Height; }
        }

        #endregion

        #region Methods

        public void AddStopValue(float stopVal)
        {
            mStopValues.Add(stopVal);
        }

        public void AddStopValue(int stopVal, int min, int max)
        {
            var val = GetValue(stopVal, min, max, false);
            mStopValues.Add(val);
        }

        public void AddStopValue(float stopVal, float min, float max)
        {
            var val = GetValue(stopVal, min, max, false);
            mStopValues.Add(val);
        }

        /// <summary>
        /// If the value is within a certain distance of a stop point, return the stop point. Otherwise return the original value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private float SnapToStopPoint(float value)
        {
            var bounds = GetPixelSize() * mSnapSize;
            foreach (var stopVal in mStopValues)
                if (value >= (stopVal - bounds) && value <= (stopVal + bounds))
                {
                    Debug.WriteLine("stopVal: " + stopVal.ToString());
                    return stopVal;
                }
            return value;
        }

        /// <summary>
        /// Returns the value change for a single pixel change.
        /// </summary>
        /// <returns></returns>
        private float GetPixelSize()
        {
            return 1f / (float)this.Distance;
        }

        private void UpdateKnobPosition()
        {
            var top = GetValue(MinTop, MaxTop);
            if (picKnob.Top == top) return;
            picKnob.Top = top;
            this.Refresh();
        }

        /// <summary>
        /// Gets the slider value for the given val that is between min and max.
        /// </summary>
        /// <param name="val"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        private float GetValue(float value, float min, float max, bool snap)
        {
            if (max <= min) throw new ArgumentException("max must be greater than min.");
            if (value < min) value = min; // throw new ArgumentException("val must be greater than or equal to min.");
            if (value > max) value = max; // throw new ArgumentException("val must be less than or equal to max.");

            var newVal = (value - min) / (max - min);
            if (snap)
                newVal = SnapToStopPoint(newVal);
            return newVal;
        }

        public float GetValue(float min, float max)
        {
            if (max <= min) throw new ArgumentException("max must be greater than min.");
            var val = (mValue * (max - min)) + min;
            return val;
        }

        public int GetValue(int min, int max)
        {
            var val = GetValue((float)min, (float)max);
            return (int)val;
        }

        public float SetValue(float value, float min, float max)
        {
            return SetValue((float)value, (float)min, (float)max, false);
        }

        public float SetValue(int value, int min, int max)
        {
            return SetValue((float)value, (float)min, (float)max, false);
        }

        public float SetValue(float value, float min, float max, bool snap)
        {
            Value = GetValue(value, min, max, snap);
            return mValue;
        }

        public float SetValue(int value, int min, int max, bool snap)
        {
            return SetValue((float)value, (float)min, (float)max, snap);
        }

        #endregion

        #region Event Handlers

        private MouseButtons mMouseButton = MouseButtons.None;
        private Point mStartLocation = Point.Empty;
        private void picKnob_MouseDown(object sender, MouseEventArgs e)
        {
            mSliding = false;
            mMouseButton = e.Button;
            mStartLocation = e.Location;
        }

        private void picKnob_MouseUp(object sender, MouseEventArgs e)
        {
            if (mSliding)
                OnEndSlide(EventArgs.Empty);

            mSliding = false;
            mMouseButton = MouseButtons.None;
            mStartLocation = Point.Empty;
        }

        private void picKnob_MouseMove(object sender, MouseEventArgs e)
        {
            if (mMouseButton != MouseButtons.Left) return;

            if (!mSliding)
            {
                mSliding = true;
                OnStartSlide(EventArgs.Empty);
            }

            var diffY = e.Y - mStartLocation.Y;
            var newTop = picKnob.Top + diffY;
            newTop = Math.Max(MinTop, newTop);
            newTop = Math.Min(MaxTop, newTop);
            SetValue(newTop, MinTop, MaxTop, Control.ModifierKeys != Keys.Control);
        }

        private void VSlider_SizeChanged(object sender, EventArgs e)
        {
            UpdateKnobPosition();
        }

        #endregion

        #region Events

        public delegate void StartSlideHandler(object sender, EventArgs e);
        public event StartSlideHandler StartSlide;
        protected virtual void OnStartSlide(EventArgs e)
        {
            if (StartSlide == null) return;
            StartSlide(this, e);
        }

        public delegate void EndSlideHandler(object sender, EventArgs e);
        public event EndSlideHandler EndSlide;
        protected virtual void OnEndSlide(EventArgs e)
        {
            if (EndSlide == null) return;
            EndSlide(this, e);
        }

        public delegate void ValueChangedHandler(object sender, EventArgs e);
        public event ValueChangedHandler ValueChanged;
        protected virtual void OnValueChanged(EventArgs e)
        {
            if (ValueChanged == null) return;
            ValueChanged(this, e);
        }

        #endregion

    }
}
